﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Security.AccessControl;
using System.Threading.Tasks;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.StorageClient;
using System.Threading;

namespace DDMySqlBackupRestore
{
    public enum MySqlBackupTrigger
    {
        testing = -100,
        minute = -10,
        halfhour = 1,
        hourly = 2,
        halfday = 3,
        daily = 4,
        monthly = 5,
        startup = 10,
        shutdown = 11

    }

    public enum ScriptType
    {
        fullbackup = 1,
        fullrestore =2

    }

    public static class BackupRestoreConfigurationNames
    {

        public const string DATABASE_NAME = "dbname";
        public const string CONTAINER_NAME = "containerName";
        public const string LOCAL_SCRIPT_PATH = "localScriptPath";
        public const string TRIGGER = "trigger";
        public const string SCRIPT_TYPE = "scriptType";
        public const string COMPRESS = "compress";
        public const string TABLE_NAME = "tableName";
       public const string ONLY_ONE_BACKUP_PER_DEPLOYMENT =  "onlyonebackupperdeployment";
       public const string LOCAL_BACKUP_FOLDER = "localBackupFolder";
       public const string DELETE_BACKUPS_OLDER_THAN = "deleteBackupsOlderThanMinutes";
       public const string CREATE_DB_SCRIPT_PATH = "createDbScriptPath";

    }
    public class MySqlBackupRestoreConfig
    {
        public const string REGEX_ROUNDBRACKETS = @"\((.*?)\)";
        public const string REGEX_SQUAREBRACKETS = @"\[(.*?)\]";
        public const string REGEX_ANGLEBRACKETS = @"\<(.*?)\>";
        public const string REGEX_CURLYBRACKETS = @"\{(.*?)\}";

        public const string PROV_HIVE_CONFIG = "com.dynamicdeploy.backuprestore.mysql.ProvisioningHive";
        public const string SCRIPT_CONFIG = "com.dynamicdeploy.backuprestore.mysql.BackupRestoreConfigProperties";

        public const string PACKAGEHIVE_CONFIG = "com.dynamicdeploy.backuprestore.mysql.PackageHive";
        public const string BACKUPFILES_CONTAINER_NAME = "ddmysqlbackupscontainer";

        public const string BACKUP_FILES_TABLE_NAME = "ddmysqlbackupstable";

        public const string LOCAL_BACKUP_FOLDER_NAME = "ddmysqlbackupsfolder";

        public const string RUNTIME_FILE_NAME_FOR_REBOOT_VALIDATION = "ddmysqlschedulerstarted.dd";

        /// <summary>
        /// Format Name ddbackupdatabaseName-Timestamp.sql
        /// </summary>
        public const string LOCAL_BACKUP_FILE_NAME_TEMPLATE = "ddbackup{0}{1}.sql";
        /// <summary>
        /// Format container/databasename/date/trigger/filename
        /// </summary>
        public const string BLOB_BACKUP_FILE_NAME_TEMPLATE = "{0}/{1}/{2}/{3}";

        /// <summary>
        /// Delete backups older than 30 days
        /// </summary>
        public const int DELETE_BACKUPS_OLDER_THAN_MINUTES = 43200;

        private string _databaseName = "";

        public string DatabaseName
        {
            get { return _databaseName; }
            set { _databaseName = value; }
        }
       
       // public string ContainerName { get; set; }

        public MySqlBackupTrigger Trigger { get; set; }
        public string AbsolutePathToScript { get; set; }
        
        public string BackupContainerName { get; set; }
        public string BackupTrackingTableName { get; set; }
   
        public bool CompressBackups { get; set; }

        public ScriptType ScriptType { get; set; }

        public bool OnlyOneBackupPerDeployment { get; set; }

        public string ProvisioningHive { get; set; }

        public string PackageHive { get; set; }

        public string LocalBackupFolder { get; set; }

        public TimeSpan DeleteBackUpsOlderThen { get; set; }

        public string CreateDbScriptPath { get; set; }

        public static readonly IList<Timer> ScheduleExecutionTimers = new List<Timer>();


        public static readonly IList<MySqlBackupRestoreConfig> AllRestoreConfigurations = new List<MySqlBackupRestoreConfig>();
        private static readonly System.Threading.TimerCallback cb = new System.Threading.TimerCallback(RunBackupSchedulesFromTimer);
        private static readonly System.Threading.TimerCallback dcb = new System.Threading.TimerCallback(RunDeleteSchedulesFromTimer);

        private static bool FillGetConfigObjectFromPathConfig(string configNVs, ref MySqlBackupRestoreConfig configObj)
        {
            bool ret = true;
            if (!string.IsNullOrEmpty(configNVs) && configObj != null)
            {
                string[] configPropArr = configNVs.Split('=');
                switch (configPropArr[0])
                {
                    case BackupRestoreConfigurationNames.COMPRESS:
                        if (!string.IsNullOrEmpty(configPropArr[1]))
                        {
                            configObj.CompressBackups = bool.Parse(configPropArr[1].Trim());
                            Trace.TraceInformation("Compression=" 
                                + configObj.CompressBackups.ToString());
                        }
                        else
                        {
                            configObj.CompressBackups = false;
                            Trace.TraceInformation("Compression parameter value not present. Will use default of no");
                        }
                        break;
                    case BackupRestoreConfigurationNames.CONTAINER_NAME:
                        

                         if (!string.IsNullOrEmpty(configPropArr[1]))
                         {
                             configObj.BackupContainerName = configPropArr[1].Trim();
                             Trace.TraceInformation("Container Name="
                                 + configObj.DatabaseName.ToString());
                         }
                         else
                         {
                             configObj.BackupContainerName = BACKUPFILES_CONTAINER_NAME;
                             Trace.TraceInformation("Container Name value not present. Will use default " + BACKUPFILES_CONTAINER_NAME);
                         }
                        break;
                    case BackupRestoreConfigurationNames.DATABASE_NAME:

                        if (!string.IsNullOrEmpty(configPropArr[1]))
                        {
                            configObj.DatabaseName = configPropArr[1].Trim();
                            Trace.TraceInformation("Database Name="
                                + configObj.DatabaseName.ToString());
                        }
                        else
                        {

                            Trace.TraceInformation("Database Name value not present. Will rely on backup restore script to provide it.");
                        }
                       
                        break;
                    case BackupRestoreConfigurationNames.LOCAL_SCRIPT_PATH:

                        string path = configPropArr[1];

                        if (!string.IsNullOrEmpty(path))
                        {
                            configObj.AbsolutePathToScript = ExpandKeywords(configPropArr[1].Trim());

                            if (!File.Exists(configObj.AbsolutePathToScript))
                            {
                                Trace.TraceError("File or Directory does not exist yet. " + configObj.AbsolutePathToScript);

                                return false;

                            }
                            else
                            {
                                FileUtils.SetFullControlDirectoryPermissions(Path.GetDirectoryName(configObj.AbsolutePathToScript));
                                Trace.TraceInformation("Added script for backup/restore " + configObj.AbsolutePathToScript);
                            }
                        }
                        break;
                    case BackupRestoreConfigurationNames.ONLY_ONE_BACKUP_PER_DEPLOYMENT:
                        

                         if (!string.IsNullOrEmpty(configPropArr[1]))
                        {
                            configObj.OnlyOneBackupPerDeployment = bool.Parse(configPropArr[1].Trim());
                            Trace.TraceInformation("OnlyOneBackupPerDeployment="
                                + configObj.OnlyOneBackupPerDeployment.ToString());
                        }
                        else
                        {
                            configObj.OnlyOneBackupPerDeployment = false;
                            Trace.TraceInformation("OnlyOneBackupPerDeployment parameter value not present. Will use default of false");
                        }
                        break;
                       
                    case BackupRestoreConfigurationNames.SCRIPT_TYPE:
                        ScriptType st;
                        if (Enum.TryParse<ScriptType>(configPropArr[1].ToLower().Trim(), out st))
                        {
                            configObj.ScriptType = st;

                        }
                        else
                        {

                            throw new ArgumentNullException(configPropArr[0], "Could not parse script type parameter " + configPropArr[1]);
                        }
                        break;
                    case BackupRestoreConfigurationNames.TABLE_NAME:
                       

                        if (!string.IsNullOrEmpty(configPropArr[1]))
                        {
                            configObj.BackupTrackingTableName = configPropArr[1].Trim();
                            Trace.TraceInformation("Azure Table Name for tracking ="
                                + configObj.BackupTrackingTableName.ToString());
                        }
                        else
                        {
                            configObj.BackupTrackingTableName = BACKUP_FILES_TABLE_NAME;
                            Trace.TraceInformation("Azure Table Name for backup tracking value not present. Using default table name " + BACKUP_FILES_TABLE_NAME);
                        }
                        break;
                    case BackupRestoreConfigurationNames.TRIGGER:
                        MySqlBackupTrigger mTrigger;
                        if (Enum.TryParse<MySqlBackupTrigger>(configPropArr[1].ToLower().Trim(), out mTrigger))
                        {
                            configObj.Trigger = mTrigger;

                        }
                        else
                        {

                            throw new ArgumentNullException(configPropArr[0], "Could not parse trigger type parameter " + configPropArr[1]);
                        }
                        break;
                    case BackupRestoreConfigurationNames.LOCAL_BACKUP_FOLDER:
                           if (!string.IsNullOrEmpty(configPropArr[1]))
                        {
                            configObj.LocalBackupFolder = ExpandKeywords(configPropArr[1].Trim());
                            Trace.TraceInformation("Local backups folder "
                                + configObj.LocalBackupFolder.ToString());
                        }
                        else
                        {

                            Trace.TraceInformation("Local Backup Folder value not present. Using default table name " + BACKUP_FILES_TABLE_NAME);
                        }

                           if (!Directory.Exists(configObj.LocalBackupFolder))
                           {
                               Trace.TraceError("Directory does not exist yet. " + configObj.LocalBackupFolder);
                               string parent = Path.GetDirectoryName(configObj.LocalBackupFolder);

                               Trace.TraceError("Getting permissions of parent. " + parent);
                               DirectorySecurity origDirSec = FileUtils.GetDirectorySecurity(parent);
                               Trace.TraceError("Setting permissions on parent. " + parent);
                               
                               FileUtils.SetFullControlDirectoryPermissions(parent);
                               Trace.TraceError("Set permissions on parent. " + parent);
                               Trace.TraceError("Now creating directory " + configObj.LocalBackupFolder);
                               Directory.CreateDirectory(configObj.LocalBackupFolder);
                               Trace.TraceError("Created directory " + configObj.LocalBackupFolder);

                               try
                               {
                                   Trace.TraceError("Setting original permissions of parent. " + parent);
                                   FileUtils.SetDirectorySecurity(parent, origDirSec);
                                   Trace.TraceError("Set original permissions of parent. " + parent);
                                   Trace.TraceError("Setting permissions on backup folder. " + configObj.LocalBackupFolder);
                                   FileUtils.SetFullControlDirectoryPermissions(configObj.LocalBackupFolder);
                                   Trace.TraceError("Set permissions on backup folder. " + configObj.LocalBackupFolder);
                               }
                               catch (Exception ex)
                               {
                                   Trace.TraceError("Error modifying permissions on directory or its parent " + configObj.LocalBackupFolder + " " + ex.Message);

                               }

                               Trace.TraceInformation("Created directory and added script for backup/restore " + configObj.AbsolutePathToScript);
                            
                           }
                           else
                           {
                               FileUtils.SetFullControlDirectoryPermissions(configObj.AbsolutePathToScript);
                               Trace.TraceInformation("Added script for backup/restore " + configObj.AbsolutePathToScript);
                           }
                        
                        break;
                        
                    case BackupRestoreConfigurationNames.DELETE_BACKUPS_OLDER_THAN:
                        if (!string.IsNullOrEmpty(configPropArr[1]))
                        {
                            configObj.DeleteBackUpsOlderThen = TimeSpan.FromMinutes(int.Parse(configPropArr[1].Trim()));
                            Trace.TraceInformation("Will delete backups older than minutes = "
                                + configPropArr[1]);
                        }
                        else
                        {
                            configObj.DeleteBackUpsOlderThen = TimeSpan.FromMinutes(DELETE_BACKUPS_OLDER_THAN_MINUTES);
                            Trace.TraceInformation("Using default value for deleting older files " + DELETE_BACKUPS_OLDER_THAN_MINUTES);
                        }

                 



                        break;
                    case BackupRestoreConfigurationNames.CREATE_DB_SCRIPT_PATH:
                        if (!string.IsNullOrEmpty(configPropArr[1]))
                        {
                            configObj.CreateDbScriptPath = ExpandKeywords( configPropArr[1].Trim());
                            Trace.TraceInformation("Create Db script found = "
                                + configObj.CreateDbScriptPath);

                            if (!File.Exists(configObj.CreateDbScriptPath))
                            {
                                Trace.TraceError("File or Directory does not exist yet. " + configObj.CreateDbScriptPath);

                                return false;

                            }
                        }
                        else
                        {
                            configObj.CreateDbScriptPath = "";
                            Trace.TraceInformation("Create DB script not found. If the database does not exist, restore won't work. ");
                        }




                        break;
                    default:

                        break;


                };


            }

            return ret;
        }
        public static bool ScheduleAndGetConfigObjectsFromConfiguration(bool runRestore = true, bool runNow = false)
        {
           bool allIsWell = true;

          
            try
            {
                string configStrs = Program.GetConfigValueAsString(SCRIPT_CONFIG);
                string provHive = Program.GetConfigValueAsString(PROV_HIVE_CONFIG);
                string packageHive = Program.GetConfigValueAsString(PACKAGEHIVE_CONFIG);

                if (!string.IsNullOrEmpty(configStrs))
                {
                   

                    

                    Regex re = new Regex(REGEX_ROUNDBRACKETS);

                    foreach (Match m in re.Matches(configStrs))
                    {
                        string eachConfigObject = m.Value.Substring(1, m.Value.Length - 2);

                        MySqlBackupRestoreConfig configObj = new MySqlBackupRestoreConfig() { 
                            ProvisioningHive = provHive, 
                            PackageHive = packageHive,
                            BackupContainerName = BACKUPFILES_CONTAINER_NAME,
                            DeleteBackUpsOlderThen = TimeSpan.FromMinutes(DELETE_BACKUPS_OLDER_THAN_MINUTES),
                            CompressBackups = false,
                            CreateDbScriptPath = "",
                            BackupTrackingTableName = BACKUP_FILES_TABLE_NAME,
                            OnlyOneBackupPerDeployment = false
                        };
                        string[] configProsArr = eachConfigObject.Split(';', ',');

                        foreach (string configNV in configProsArr)
                        {
                            try
                            {
                                

                               if(!FillGetConfigObjectFromPathConfig(configNV, ref configObj))
                               {

                                   return false;
                               }
                               
                              
                               
                            }
                            catch (Exception ex)
                            {
                                Trace.TraceError("Error loading configuration object from configuration string " + configStrs + Environment.NewLine + ex.Message);
                                allIsWell = false;
                            }
                        }//foreach nv

                        try
                        {

                                if (configObj.ScriptType == ScriptType.fullbackup)
                                {
                                    if (!runNow)
                                    {
                                                                            
                                        Timer s = GetScheduleFromTrigger(configObj, configObj.Trigger);
                                        
                                        ScheduleExecutionTimers.Add(s);
                                        try
                                        {
                                            Timer delT = new Timer(dcb, configObj,Program.BACKUP_INITIAL_WAIT_MILLIS, (int)configObj.DeleteBackUpsOlderThen.TotalMilliseconds);

                                            ScheduleExecutionTimers.Add(delT);
                                        }
                                        catch (Exception ex)
                                        {

                                            Trace.TraceWarning("Error scheduling delete task " + ex.Message);
                                        }


                                        Trace.TraceInformation(string.Format("Added schedule  for execution on path {0}", configObj.AbsolutePathToScript));
                                    }
                                    else
                                    {

                                        RunScheduleWithRetries(1, configObj);
                                    }
                                }
                        

                            #region Restore the database

                            if (runRestore)
                            {
                                CreateDatabaseAndRestore(configObj);
                            }

                            #endregion
                            //if (configObj.ScriptType == ScriptType.fullbackup)
                            //{
                            //    AllBackupConfiguration.Add(configObj);
                            //}

                        }
                        catch (Exception ex)
                        {
                            Trace.TraceError("Error loading configuration object from configuration string " + configStrs + Environment.NewLine + ex.Message);
                            allIsWell = false;
                        }
                    }//for regex match

                }
                else
                {

                    Trace.TraceError("GetConfigObjectFromConfiguration() backup restore configurations were empty.");
                    allIsWell = false;
                }

            }
            catch (Exception ex)
            {

                Trace.TraceError("GetConfigObjectFromConfiguration() Error loading configuration for backup restore " + ex.Message);
                allIsWell = false;
            }

            if (!allIsWell)
            {
                foreach (Timer t in ScheduleExecutionTimers)
                {
                    if (t != null)
                    {
                        t.Dispose();
                    }

                }
                ScheduleExecutionTimers.Clear();
            }
          

            return allIsWell;
        }


        public static void CreateDatabaseAndRestore(MySqlBackupRestoreConfig configObj)
        {
            try
            {
                if (configObj.ScriptType == ScriptType.fullrestore)
                {
                    AllRestoreConfigurations.Add(configObj);

                    Trace.TraceInformation(string.Format("Added restore script {0}", configObj.DatabaseName));

                    bool shouldIRunRestore = false;

                    if (!Program.IsReboot)
                    {
                        shouldIRunRestore = true;
                        Trace.TraceInformation("Fresh install. Will run restore operations " + configObj.DatabaseName);
                    }
                    else
                        if (Program.IsReboot && configObj.Trigger == MySqlBackupTrigger.startup)
                        {
                            shouldIRunRestore = true;
                            Trace.TraceInformation("Just a reboot but startup trigger is true. Will run restore operations " + configObj.DatabaseName);
                        }
                        else
                        {
                            Trace.TraceInformation("Just a reboot. Will not run restore operations " + configObj.DatabaseName);

                        }

                    if (shouldIRunRestore)
                    {
                        //Create a tracking object
                        RestoreTrackingTable rt = new RestoreTrackingTable() { CreatedDb = false, CreateDbStatus = "" };
                        //Run create db
                        if (!string.IsNullOrEmpty(configObj.CreateDbScriptPath))
                        {
                           
                            try
                            {

                                if (BatchScriptsExecutor.ExecuteBatchFile(configObj.CreateDbScriptPath, null))
                                {
                                    rt.CreatedDb = true;
                                    rt.CreateDbStatus = "Executed create database script " + configObj.CreateDbScriptPath;
                                    Trace.TraceInformation("Executed create database script " + configObj.CreateDbScriptPath);
                                }
                                else
                                {
                                    rt.CreateDbStatus = "Received error executing create database script" + configObj.CreateDbScriptPath;
                                    Trace.TraceInformation("Received error executing create database script " + configObj.CreateDbScriptPath);

                                }


                            }
                            catch (Exception ex)
                            {

                                Trace.TraceError(string.Format("Error creating new database {0} using script {1}. Exception:{2}" + ex.Message, configObj.CreateDbScriptPath, ex.Message));
                            }


                        }
                        else
                        {
                            Trace.TraceWarning("Create database script not found. Will assume database exists else restores will fail.Restore Script: " + configObj.AbsolutePathToScript);

                        }

                        RestoreDatabase(3, configObj, rt);

                     
                    }


                }//shouldIrunrestore
            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Error running backup ahd restore restore database on {0}.  Exception:{2}", configObj.AbsolutePathToScript, ex.Message));
               

            }

        }

        public static string GetLatestBackupFileFromBlob(MySqlBackupRestoreConfig configObj, out DDBackupTrackingTable backupRecord)
        {
            backupRecord = null;
            var backups = DDBackupTrackingTableUtil.GetLatestDatabaseBackups(configObj.DatabaseName, configObj.PackageHive, configObj.ProvisioningHive, configObj.Trigger, 1);

            if (backups != null)
            {

               DDBackupTrackingTable record = backups.First<DDBackupTrackingTable>();

               if (record != null)
               {

                //   BlobHelper bh = new BlobHelper(SyncService.GetConfigValueAsString("com.dynamicdeploy.backuprestore.mysql.DataConnectionString"));
                   backupRecord = record;
                   CloudBlob backupBlob = CloudStorageAccount.Parse(Program.GetConfigValueAsString("com.dynamicdeploy.backuprestore.mysql.DataConnectionString")).CreateCloudBlobClient().GetContainerReference(record.BackupFileContainerName).GetBlobReference(record.BackupFileBlobName);
                  string localPath = record.LocalFilePath;
                   if (!File.Exists(localPath))
                   {
                       backupBlob.DownloadToFile(localPath, new BlobRequestOptions() { RetryPolicy = Program.RETRY_POLICY, Timeout = TimeSpan.FromMinutes(5) });

                   }
                  
                   return localPath;

               }
            }

            return null;
        }
        public static void RestoreDatabase(int retries, MySqlBackupRestoreConfig configObj,  RestoreTrackingTable rt)
        {
            if (retries == 0)
            {
                Trace.TraceError("Exceeded retries for restoring " + configObj.DatabaseName);
                return;

            }

            if (rt == null)
            {

                rt = new RestoreTrackingTable();
            }
            try
            {
              
               
                //Run Restore now.
                if (configObj.ScriptType == ScriptType.fullrestore )
                {
                    try
                    {
                       
                        DDBackupTrackingTable record;
                        string localBackupPath = GetLatestBackupFileFromBlob(configObj, out record);

                        if (!string.IsNullOrEmpty(localBackupPath) && File.Exists(localBackupPath))
                        {
                            if (BatchScriptsExecutor.ExecuteBatchFile(configObj.AbsolutePathToScript, new string[] { configObj.DatabaseName, localBackupPath }))
                           
                            {
                                rt.BackupFileBlobName = record.BackupFileBlobName;
                                rt.BackupFileContainerName = configObj.BackupContainerName;
                                rt.DateRestored = DateTime.UtcNow;
                                rt.PartitionKey = SecUtility.PartitionAndRowKeyEscape( RestoreTrackingTable.CreatePartitionKey(Program.DEPLOYMENT_ID,
                                    Program.ROLE_ID, Program.INSTANCE_ID));
                                rt.RowKey = RestoreTrackingTable.CreateRowKey(configObj.DatabaseName);
                                rt.Status = 1;
                                rt.StatusMessage = "Executed restore database script " + localBackupPath;

                                RestoreTrackingTableUtil.SaveRestoreTracking(rt);
                                Trace.TraceInformation("Executed restore database script " + localBackupPath);

                                return;
                            }
                            else
                            {
                                Trace.TraceInformation("Received error executing restore database script " + localBackupPath);
                                System.Threading.Thread.Sleep(Program.TIMEINSECSFORINFINITEINITWAIT * 1000);
                                RestoreDatabase(--retries, configObj, rt);

                            }

                        }
                        else
                        {

                            Trace.TraceError("Restore file not found " + localBackupPath);

                        }
                    }
                    catch (Exception ex)
                    {

                        Trace.TraceError(string.Format("Error restoring database {0} using script {1}. Exception:{2}", configObj.DatabaseName, configObj.CreateDbScriptPath, ex.Message));
                        System.Threading.Thread.Sleep(Program.TIMEINSECSFORINFINITEINITWAIT * 1000);
                        RestoreDatabase(--retries, configObj, rt);
                    }


                }

                //This is not a restore script
                return;

            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Error running restore database on {0}. Retries remaining {1}. Exception:{2}", configObj.AbsolutePathToScript, retries, ex.Message));
                System.Threading.Thread.Sleep(Program.TIMEINSECSFORINFINITEINITWAIT * 1000);
                RestoreDatabase(--retries, configObj, rt);

            }

        }
        public static string ExpandKeywords(string data)
        {

            return Program.ExpandKeywords(data);

        }
        public static Timer GetScheduleFromTrigger(MySqlBackupRestoreConfig config, MySqlBackupTrigger trigger)
        {
          
           
            switch (trigger)
            {
                case MySqlBackupTrigger.minute:

                    return new Timer(cb, config, Program.BACKUP_INITIAL_WAIT_MILLIS, 60 * 1000);

                case MySqlBackupTrigger.daily:

                    return new Timer(cb, config, Program.BACKUP_INITIAL_WAIT_MILLIS, 60 * 60 * 24 * 1000);


                case MySqlBackupTrigger.halfday:
                    return new Timer(cb, config, Program.BACKUP_INITIAL_WAIT_MILLIS, 60 * 60 * 12 * 1000);

                case MySqlBackupTrigger.halfhour:
                    return new Timer(cb, config, Program.BACKUP_INITIAL_WAIT_MILLIS, 60 * 30 * 1000);
                case MySqlBackupTrigger.hourly:
                    return new Timer(cb, config, Program.BACKUP_INITIAL_WAIT_MILLIS, 60 * 60 * 1000);
                case MySqlBackupTrigger.monthly:
                    return new Timer(cb, config, Program.BACKUP_INITIAL_WAIT_MILLIS, 60 * 60 * 24 * 15 * 1000);
                default:

                    return null;

            };

        }


        public static void RunBackupSchedulesFromTimer(object state)
        {
             MySqlBackupRestoreConfig configObj = state as MySqlBackupRestoreConfig;
            try
            {
               

                if(configObj != null)
                {
                    RunScheduleWithRetries(3, configObj);


                }


            }
            catch (Exception ex)
            {

                Trace.TraceError("Error running backup schedule " + ex.Message);
            }


        }

        public static void RunDeleteSchedulesFromTimer(object state)
        {
              MySqlBackupRestoreConfig configObj = state as MySqlBackupRestoreConfig;
            try
            {
                if (configObj != null)
                {
                    DeleteOlderFileList(configObj.LocalBackupFolder, DateTime.UtcNow.AddMinutes(-1 * configObj.DeleteBackUpsOlderThen.TotalMinutes));
                }

            }
            catch (Exception ex)
            {
                Trace.TraceError("Error running delete schedule " + ex.Message);

            }

        }

        public static void RunScheduleWithRetries(int retries, MySqlBackupRestoreConfig config)
        {
            if (retries == 0)
            {
                //  RoleEnvironment.RequestRecycle();
                Trace.TraceError("RunScheduleWithRetries() retries exceeded with fatal errors. Retry count: {0}", retries);
                return;
            }


            try
            {
                string backupFilePath = Path.Combine(config.LocalBackupFolder, string.Format(MySqlBackupRestoreConfig.LOCAL_BACKUP_FILE_NAME_TEMPLATE, config.DatabaseName, (DateTime.MaxValue - DateTime.UtcNow).Ticks.ToString("d19")));


                if (BatchScriptsExecutor.ExecuteBatchFile(config.AbsolutePathToScript, new string[] { config.DatabaseName, backupFilePath }))
                {
                    //Upload the backup file to the blob
                    Thread.Sleep(TimeSpan.FromSeconds(3));
                    UploadBackupFile(backupFilePath, config);
                    return;
                }
                else
                {
                    throw new Exception("Returned false from ExecuteBatchFile for batch backup script " + config.AbsolutePathToScript);

                }

            }
            catch (Exception ex)
            {

                Trace.TraceError("Error running backup, will submit for retries." + ex.Message);
                //SLeep for 60 secs to clear up any locks and stuff
                System.Threading.Thread.Sleep(60);
                RunScheduleWithRetries(--retries, config);
            }


        }

        static void UploadBackupFile(string backupFilePath, MySqlBackupRestoreConfig configObj)
        {

            try
            {


                if (File.Exists(backupFilePath) && new FileInfo(backupFilePath).Length > 1000)
                {


                    var backupContainer = CloudStorageAccount.Parse(Program.GetConfigValueAsString("com.dynamicdeploy.backuprestore.mysql.DataConnectionString")).CreateCloudBlobClient().GetContainerReference(configObj.BackupContainerName);
                    backupContainer.CreateIfNotExist();
                    string customDT = DateTime.UtcNow.ToString(Program.CUSTOM_DATE_FORMAT_SHORT).Replace(" ", "");
                    //format deploymentId/rolename/instanceid/databaseName/date/backup.sql
                    string blobName = Path.GetFileName(backupFilePath);
                    string blobRef = string.Format("{0}/{1}/{2}/{3}/{4}/{5}", Program.DEPLOYMENT_ID, Program.ROLE_ID, Program.INSTANCE_ID, configObj.DatabaseName, customDT, blobName);
                    var backupBlob = backupContainer.GetBlobReference(blobRef);
                    //backupBlob.Properties.ContentType = "text/plain";


                    Trace.TraceInformation("Uploading blob to storage " + blobRef);

                    backupBlob.UploadFile(backupFilePath, new BlobRequestOptions() { RetryPolicy = Program.RETRY_POLICY, Timeout = TimeSpan.FromSeconds(300) });
                    Trace.TraceInformation("Uploaded blob to storage " + blobRef);

                    Trace.TraceInformation("Updating table fpr backup " + blobRef);
                    DDBackupTrackingTable dt = new DDBackupTrackingTable()
                    {
                        BackupFileBlobName = blobRef,
                        BackupFileContainerName = configObj.BackupContainerName,
                        DateBackupWasCreated = DateTime.UtcNow,
                        IsCompressed = false,
                        PackageHive = configObj.PackageHive,
                        PartitionKey = SecUtility.PartitionAndRowKeyEscape(DDBackupTrackingTable.CreatePartitionKey(configObj.DatabaseName, configObj.PackageHive, configObj.ProvisioningHive)),
                        ProvisioningHive = configObj.ProvisioningHive,
                        RoleId = string.Format("{0}/{1}/{2}", Program.DEPLOYMENT_ID, Program.ROLE_ID, Program.INSTANCE_ID),
                        RowKey = DDBackupTrackingTable.CreateRowKey(),
                        TriggerType = configObj.Trigger.ToString(),
                         LocalFilePath = backupFilePath
                    };

                    if (DDBackupTrackingTableUtil.SaveBackupTracking(dt))
                    {

                        Trace.TraceInformation("Updated table for backup " + blobRef);
                    }
                    else
                    {
                        Trace.TraceInformation("Error updating table for backup " + blobRef);
                    }

                }
                else
                {

                    Trace.TraceError("Backup file path not found or size is too small to upload. " + backupFilePath);
                }
            }
            catch (Exception ex)
            {
                Trace.TraceError(string.Format("Error uploading backup file {0} to container {1}. Exception:{2} ", backupFilePath, configObj.BackupContainerName, ex.Message));
            }
        }
       

        /// <summary>
        /// Deletes the list of log files in a directory older than specified data
        /// </summary>
        /// <param name="FileDir">the directory to search</param>
        /// <param name="NewFileDate">
        ///    files older than this date will be outputted. 
        ///    newer files will be skipped.
        /// </param>
        public static void DeleteOlderFileList(string FileDir, DateTime NewFileDate, string fileExtensionToFilterOn = "*.*")
        {
            // get the list of all log files in FileDir
            List<string> LogFiles = System.IO.Directory.GetFiles(FileDir, fileExtensionToFilterOn).ToList();

            // filter the list to only include files older than NewFileDate
            List<string> OutputList = LogFiles.Where(x => System.IO.File.GetCreationTime(x) < NewFileDate).ToList();

            // output the list
            Parallel.ForEach<string>(OutputList, logFile =>
            {

                try
                {
                    File.Delete(logFile);
                  // Trace.TraceInformation("Deleted local backup " + logFile);
                }
                catch (Exception ex)
                {
                    Trace.TraceError("Error deleting file. Will try on the next run " + ex.Message);

                }

            });
        } 
    }



    

    
}
